const fs = require("fs-extra");
const path = require("path");
const Decrypter = require('aes-decrypter').Decrypter;
const runPara = require("run-parallel-limit");
const url = require("url");
const M3U8 = require("m3u8-parser");
const axios = require("axios").default.create({
  headers: {
    Origin: 'http://xue.eoffcn.com',
    Referer: 'http://xue.eoffcn.com/web/video.html?lesson_id=240762&package_id=1722&course_type=2',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137'
  }
});
const cp = require("child_process");
const lib = require("./lib");

function chuliEncryptedVideoKey(encryptedVideoKey) {
  var _encryptedVideoKey = encryptedVideoKey;
  let _key = lib.utf8_toBytes('72Fhskjglp8qjpqx');
  let _aesEcb = new lib.ModeOfOperationECB(_key);
  var _bbb = [];
  for (var i = 0; i < _encryptedVideoKey.length; i += 2) {
    _bbb.push(parseInt(_encryptedVideoKey[i] + _encryptedVideoKey[i + 1], 16));
  }
  var _decryptedBytes = _aesEcb.decrypt(_bbb);
  var _decryptedText = lib.convertUtf8.fromBytes(_decryptedBytes)
  let key = lib.str2ab(_decryptedText);
  let view = new DataView(key);
  let segmentKeyBytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
  // debugger
  return segmentKeyBytes;
}

/**
 * @returns {Promise<{ok:Boolean,msg:String,
 * data:{
 * iv:Uint32Array,
 * key_bytes:Uint32Array,
 * segments_links:String[]
 * }}>}
 * @param {String} link 
 */
function parseM3u8Link(link) {
  let parsed_link = url.parse(link);
  // debugger
  let getKeyBytes = (key_link) => new Promise(r => {
    axios.get(key_link).then(axresp => {
      let encryptedVideoKey = axresp.data.encryptedVideoKey;
      return r(chuliEncryptedVideoKey(encryptedVideoKey));
      debugger
    }).catch(axerr => {
      debugger
    })
  })
  return new Promise(resolve => {
    axios.get(link).then(async axresp => {
      let parser = new M3U8.Parser();
      parser.push(axresp.data);
      if (parser.manifest.segments.length
        && parser.manifest.segments[0].key
        && parser.manifest.segments[0].key.iv) {
        let iv = parser.manifest.segments[0].key.iv;
        let segments_links = parser.manifest.segments.map(e => {
          return link.replace(path.basename(parsed_link.pathname), e.uri);
        });
        let key_link = parser.manifest.segments[0].key.uri + "&playerId=pid-1-5-1";
        let o_keyBytes = await getKeyBytes(key_link);
        return resolve({
          ok: true,
          msg: "ok",
          data: {
            segments_links: segments_links,
            iv: iv,
            key_bytes: o_keyBytes
          }
        })
        debugger

      }
      // debugger
    }).catch(axerr => {
      debugger
    })
  })
}

/**
 * 
 * @param {String} m3u8link 
 * @param {String} videoName 
 */
function downloadByM3u8(m3u8link, videoName) {
  /**
   * @returns {Promise<{ts_local_path:String}>}
   * @param {String} segment_link 
   * @param {Uint32Array} iv 
   * @param {Uint32Array} keybytes 
   */
  let downloadAndDecrypto = (segment_link, iv, keybytes) => new Promise(r => {
    axios.get(segment_link, {
      responseType: "arraybuffer"
    }).then(axresp => {
      // debugger
      new Decrypter(axresp.data, keybytes, iv, (err, bytes) => {
        let lcpath = path.join(__dirname, path.basename(segment_link));
        fs.writeFile(lcpath, bytes, (err) => {
          if (err) {
            debugger
          }
          return r({ ts_local_path: lcpath });
        })
        // debugger
      })
    }).catch(axerr => {
      debugger
    })
  })
  return new Promise(async resolve => {
    let parse_m3u8 = await parseM3u8Link(m3u8link);
    let paraTasksresult = [];
    let tasks = parse_m3u8.data.segments_links.map((e, i) => async cb => {
      let o_down = await downloadAndDecrypto(e, parse_m3u8.data.iv, parse_m3u8.data.key_bytes);
      paraTasksresult.push({
        local_file: o_down.ts_local_path,
        index: i
      });
      cb();
    });
    runPara(tasks, 5, async () => {
      // debugger
      let local_files = paraTasksresult.sort((a, b) => a.index - b.index).map(e => e.local_file);
      await fs.writeFile(path.join(__dirname, "filelist.txt"), local_files
        .map(e => `file '${path.basename(e)}'`).join("\n"));
      cp.exec(`ffmpeg.exe -f concat -i filelist.txt -c copy "${videoName}.ts"`, {
        cwd: __dirname
      }, async (err, stdout, stderr) => {
        if (err) {
          debugger
        }
        for (let tsfile of local_files) {
          await fs.unlink(tsfile);
        }
        let matched = stderr.match(/video:[0-9]+.*audio:[0-9]+.*subtitle:[0-9]+/g)
        if (matched) {
          console.log(m3u8link, videoName, "OK!")
        }else{
          console.log(m3u8link, videoName, "FAIL?",stderr)
        }
        // debugger
        resolve();
      })

    })
    // debugger
  })
}

downloadByM3u8("http://gcik47gyt746q6nqdze.exp.bcevod.com/mda-igfeyn3cvxyuxifk/mda-igfeyn3cvxyuxifk.m3u8", "测试 1").then(o => {
  debugger
})